﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace DotNet
{
    public class FuncLamda
    {
        /*
         * Invoke(): run function
         * Complile(): build function
         */

        public void Lamda()
        {
            Func<int, string> lamda = x =>
                                      {
                                          var tmp = x;
                                          return tmp.ToString();
                                      };

            Func<int, Func<int, string>> lamdaComplex = x =>
                                                        {
                                                            return name =>
                                                                   {
                                                                       var tmp = name.ToString();
                                                                       return tmp;
                                                                   };
                                                        };

            Console.WriteLine(lamda(123));
            Console.WriteLine(lamdaComplex(123).Invoke(456));

            Test();

            // Invoke, Compile
            Func<int, int> func = null;
            Expression<Func<int, int>> bind = (x) => func(x);

            Expression expr = Expression.Invoke(bind, Expression.Constant(5));

            Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(expr);
            Func<int> compiled = lambda.Compile();

            Console.WriteLine(expr);

            func = x => 3 * x;
            Console.WriteLine(compiled());

            func = x => 7 * x;
            Console.WriteLine(compiled());
        }

        public void PredicateBuilder()
        {
            //var predicate = PredicateBuilder.True<employee>();

            //if (!string.IsNullOrEmpty(txtAddress.Text))
            //    predicate = predicate.And(e1 => e1.Address.Contains(txtAddress.Text));
            //if (!string.IsNullOrEmpty(txtEmpId.Text))
            //    predicate = predicate.And(e1 => e1.Id == Convert.ToInt32(txtEmpId.Text));
            //if (!string.IsNullOrEmpty(txtDesc.Text))
            //    predicate = predicate.And(e1 => e1.Desc.Contains(txtDesc.Text));
            //if (!string.IsNullOrEmpty(txtName.Text))
            //    predicate = predicate.And(e1 => e1.Name.Contains(txtName.Text));

            //EmployeeDataContext edb = new EmployeeDataContext();
            //var emp = edb.Employees.Where(predicate);
            //grdEmployee.DataSource = emp.ToList();
            //grdEmployee.DataBind();
        }

        public static class ParameterReplacer
        {
            // Produces an expression identical to 'expression'
            // except with 'source' parameter replaced with 'target' expression.     
            public static Expression<TOutput> Replace<TInput, TOutput>
                            (Expression<TInput> expression,
                            ParameterExpression source,
                            Expression target)
            {
                return new ParameterReplacerVisitor<TOutput>(source, target)
                            .VisitAndConvert(expression);
            }

            private class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
            {
                private ParameterExpression _source;
                private Expression _target;

                public ParameterReplacerVisitor
                        (ParameterExpression source, Expression target)
                {
                    _source = source;
                    _target = target;
                }

                internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
                {
                    return (Expression<TOutput>)VisitLambda(root);
                }

                protected override Expression VisitLambda<T>(Expression<T> node)
                {
                    // Leave all parameters alone except the one we want to replace.
                    var parameters = node.Parameters
                                         .Where(p => p != _source);

                    return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
                }

                protected override Expression VisitParameter(ParameterExpression node)
                {
                    // Replace the source with the target, visit other params as usual.
                    return node == _source ? _target : base.VisitParameter(node);
                }
            }
        }

        public class Foo
        {
            public int a { get; set; }
            public int b { get; set; }
        }

        private void Test()
        {
            List<Foo> foos = new List<Foo>();
            foos.Add(new Foo());
            foos.Add(new Foo());
            Expression<Func<Foo, int>> exp0 = f => f.a * f.b;
            Expression<Func<int>> exp1 = () => foos[0].a * foos[0].b;
            Expression<Func<int>> exp2 = () => foos[1].a * foos[1].b;

            //Usage for your scenario (tested):
            var zeroIndexIndexer = Expression.MakeIndex
                (Expression.Constant(foos),
                    typeof(List<Foo>).GetProperty("Item"),
                    new[] { Expression.Constant(0) });
            // .ToString() of the below looks like the following: 
            //  () =>    (value(System.Collections.Generic.List`1[App.Foo]).Item[0].a
            //         *  value(System.Collections.Generic.List`1[App.Foo]).Item[0].b)
            var exp1Clone = ParameterReplacer.Replace<Func<Foo, int>, Func<int>>(exp0, exp0.Parameters.Single(), zeroIndexIndexer);
        }
    }
}
